#include "drv_sdio.h"

#include <string.h>
#include "ff.h"

#include "stdint.h"

#include "bsp_pinctrl.h"

/* SDIOC DMA configuration define */
#define SDIOC_DMA_UNIT                  (CM_DMA1)
#define SDIOC_DMA_CLK                   (FCG0_PERIPH_DMA1 | FCG0_PERIPH_AOS)
#define SDIOC_DMA_TX_CH                 (DMA_CH0)
#define SDIOC_DMA_RX_CH                 (DMA_CH1)
#define SDIOC_DMA_TX_TRIG_CH            (AOS_DMA1_0)
#define SDIOC_DMA_RX_TRIG_CH            (AOS_DMA1_1)
#define SDIOC_DMA_TX_TRIG_SRC           (EVT_SRC_SDIOC1_DMAW)
#define SDIOC_DMA_RX_TRIG_SRC           (EVT_SRC_SDIOC1_DMAR)

/* SDIOC configuration define */
#define SDIOC_MMC_UINT                  (CM_SDIOC1)
#define SDIOC_MMC_CLK                   (FCG1_PERIPH_SDIOC1)
#define SIDOC_MMC_INT_SRC               (INT_SRC_SDIOC1_SD)
#define SIDOC_MMC_IRQ                   (INT006_IRQn)

/* SDIOC configuration define */
#define SDIOC_SD_UINT                   (CM_SDIOC1)
#define SDIOC_SD_CLK                    (FCG1_PERIPH_SDIOC1)
#define SIDOC_SD_INT_SRC                (INT_SRC_SDIOC1_SD)
#define SIDOC_SD_IRQ                    (INT006_IRQn)

static stc_sd_handle_t SdHandle;
static stc_mmc_handle_t MmcHandle;

static SemaphoreHandle_t xSDIOCSemaphore;
static __IO uint8_t u8TxRxErrorFlag = 0;
static uint8_t u8SDIOCInitFlag = 0;

uint8_t SDIOC_GetTxRxErr(void)
{
    return u8TxRxErrorFlag;
}

void SDIOC_ClrTxRxErr(void)
{
    u8TxRxErrorFlag = 0;
}

uint8_t SDIOC_IsInited(void)
{
    return u8SDIOCInitFlag;
}

SemaphoreHandle_t SDIOC_GetSem(void)
{
    return xSDIOCSemaphore;
}

void SDIOC_CompleteCallback(void)
{
    BaseType_t xTaskWokenByReceive = pdFALSE;
    xSemaphoreGiveFromISR(xSDIOCSemaphore, &xTaskWokenByReceive);
    
    if( xTaskWokenByReceive != pdFALSE )
    {
        /* We should switch context so the ISR returns to a different task.
           NOTE: How this is done depends on the port you are using. Check
           the documentation and examples for your port. */
        taskYIELD ();
    }
}

/**
 * @brief  SDIOC TX complete callback function.
 * @param  None
 * @retval None
 */
#ifdef CARD_SDC
void SD_TxCompleteCallback(stc_sd_handle_t *handle)
#else
void MMC_TxCompleteCallback(stc_mmc_handle_t *handle)
#endif // CARD_SDC
{
    SDIOC_CompleteCallback();
//    u8TxCompleteFlag = 1U;
}

/**
 * @brief  SDIOC RX complete callback function.
 * @param  None
 * @retval None
 */
#ifdef CARD_SDC
void SD_RxCompleteCallback(stc_sd_handle_t *handle)
#else
void MMC_RxCompleteCallback(stc_mmc_handle_t *handle)
#endif // CARD_SDC
{
    SDIOC_CompleteCallback();
//    u8RxCompleteFlag = 1U;
}

/**
 * @brief  SDIOC error callback function.
 * @param  None
 * @retval None
 */
#ifdef CARD_SDC
void SD_ErrorCallback(stc_sd_handle_t *handle)
#else
void MMC_ErrorCallback(stc_mmc_handle_t *handle)
#endif // CARD_SDC
{
    u8TxRxErrorFlag = 1;
    SDIOC_CompleteCallback();
}

#ifdef CARD_SDC
/**
 * @brief  SDIOC transfer complete interrupt callback function.
 * @param  None
 * @retval None
 */
static void SdCard_TransCompleteIrqCallback(void)
{
    SD_IRQHandler(&SdHandle);
}

stc_sd_handle_t* SdCard_GetHandle(void)
{
    return &SdHandle;
}

#else
/**
 * @brief  SDIOC transfer complete interrupt callback function.
 * @param  None
 * @retval None
 */
static void MmcCard_TransCompleteIrqCallback(void)
{
    MMC_IRQHandler(&MmcHandle);
}

stc_mmc_handle_t* MMC_GetHandle(void)
{
    return &MmcHandle;
}

static void MmcCard_DMAInit(void)
{
    stc_dma_init_t stcDmaInit;

    /* Enable DMA and AOS clock */
    FCG_Fcg0PeriphClockCmd(SDIOC_DMA_CLK, ENABLE);

    (void)DMA_StructInit(&stcDmaInit);
    /* Configure MMC DMA Transfer */
    stcDmaInit.u32IntEn         = DMA_INT_DISABLE;
    stcDmaInit.u32DataWidth     = DMA_DATAWIDTH_32BIT;
    stcDmaInit.u32SrcAddrInc    = DMA_SRC_ADDR_INC;
    stcDmaInit.u32DestAddrInc   = DMA_DEST_ADDR_FIX;
    if (LL_OK != DMA_Init(SDIOC_DMA_UNIT, SDIOC_DMA_TX_CH, &stcDmaInit)) {
        for (;;) {
        }
    }
    /* Configure SD DMA Receive */
    stcDmaInit.u32SrcAddrInc    = DMA_SRC_ADDR_FIX;
    stcDmaInit.u32DestAddrInc   = DMA_DEST_ADDR_INC;
    if (LL_OK != DMA_Init(SDIOC_DMA_UNIT, SDIOC_DMA_RX_CH, &stcDmaInit)) {
        for (;;)
        {}
    }

    AOS_SetTriggerEventSrc(SDIOC_DMA_TX_TRIG_CH, SDIOC_DMA_TX_TRIG_SRC);
    AOS_SetTriggerEventSrc(SDIOC_DMA_RX_TRIG_CH, SDIOC_DMA_RX_TRIG_SRC);
    /* Enable DMA */
    DMA_Cmd(SDIOC_DMA_UNIT, ENABLE);
}
#endif // CARD_SDC

/**
 * @brief  SD card configuration.
 * @param  None
 * @retval None
 */
void SDIOC_Config(void)
{
    stc_irq_signin_config_t stcIrqSignConfig;

    Pinctrl_SDIOC_Init();
    
#ifdef CARD_SDC
    /* Interrupt configuration */
    stcIrqSignConfig.enIntSrc    = SIDOC_SD_INT_SRC;
    stcIrqSignConfig.enIRQn      = SIDOC_SD_IRQ;
    stcIrqSignConfig.pfnCallback = &SdCard_TransCompleteIrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSignConfig);
    NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
    NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_02);
    NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);
#else
    /* Interrupt configuration */
    stcIrqSignConfig.enIntSrc    = SIDOC_MMC_INT_SRC;
    stcIrqSignConfig.enIRQn      = SIDOC_MMC_IRQ;
    stcIrqSignConfig.pfnCallback = &MmcCard_TransCompleteIrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSignConfig);
    NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
    NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_02);
    NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);
#endif // CARD_SDC
    
    /* Enable SDIOC clock */
    FCG_Fcg1PeriphClockCmd(SDIOC_SD_CLK, ENABLE);

#ifdef CARD_SDC
    /* Configure structure initialization */
    SdHandle.SDIOCx                     = SDIOC_SD_UINT;
    SdHandle.stcSdiocInit.u32Mode       = SDIOC_MD_SD;
    SdHandle.stcSdiocInit.u8CardDetect  = SDIOC_CARD_DETECT_CD_PIN_LVL;
    SdHandle.stcSdiocInit.u8SpeedMode   = SDIOC_SPEED_MD_HIGH;
    SdHandle.stcSdiocInit.u8BusWidth    = SDIOC_BUS_WIDTH_4BIT;
    SdHandle.stcSdiocInit.u16ClockDiv   = SDIOC_CLK_DIV4;
    SdHandle.DMAx    = NULL;
#else
    /* Configure structure initialization */
    MmcHandle.SDIOCx                    = SDIOC_MMC_UINT;
    MmcHandle.stcSdiocInit.u32Mode      = SDIOC_MD_MMC;
    MmcHandle.stcSdiocInit.u8CardDetect = SDIOC_CARD_DETECT_CD_PIN_LVL;
    MmcHandle.stcSdiocInit.u8SpeedMode  = SDIOC_SPEED_MD_HIGH;
    MmcHandle.stcSdiocInit.u8BusWidth   = SDIOC_BUS_WIDTH_4BIT;
    MmcHandle.stcSdiocInit.u16ClockDiv  = SDIOC_CLK_DIV4;

    MmcCard_DMAInit();
    MmcHandle.DMAx      = SDIOC_DMA_UNIT;
    MmcHandle.u8DmaTxCh = SDIOC_DMA_TX_CH;
    MmcHandle.u8DmaRxCh = SDIOC_DMA_RX_CH;

#endif // CARD_SDC
    
    if(xSDIOCSemaphore == NULL)
    {
        xSDIOCSemaphore = xSemaphoreCreateBinary();
    }

    u8SDIOCInitFlag = 0;
#ifdef CARD_SDC
    /* Reset and init SDIOC */
    if (LL_OK != SDIOC_SWReset(SdHandle.SDIOCx, SDIOC_SW_RST_ALL)) {
        DDL_Printf("Reset SDIOC failed!\r\n");
    } else if (LL_OK != SD_Init(&SdHandle)) {
        DDL_Printf("SD card initialize failed!\r\n");
    } else {
        DDL_Printf("SD card initialize OK!\r\n");
        u8SDIOCInitFlag = 1;
    }
#else
    /* Reset and init SDIOC */
    if (LL_OK != SDIOC_SWReset(MmcHandle.SDIOCx, SDIOC_SW_RST_ALL)) {
        DDL_Printf("Reset SDIOC failed!\r\n");
    } else if (LL_OK != MMC_Init(&MmcHandle)) {
        DDL_Printf("MMC card initialize failed!\r\n");
    } else {
        DDL_Printf("SD card initialize OK!\r\n");
        u8SDIOCInitFlag = 1;
    }
#endif // CARD_SDC
}

void fat_time_to_str(WORD date, WORD time, char* buffer) {
    int year = ((date >> 9) & 0x7F) + 1980;
    int month = (date >> 5) & 0x0F;
    int day = date & 0x1F;
    int hour = (time >> 11) & 0x1F;
    int minute = (time >> 5) & 0x3F;
    int second = (time & 0x1F) * 2;
    
    sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d", 
            year, month, day, hour, minute, second);
}

void format_file_size(DWORD size, char* buffer) {
    if (size < 1024) {
        sprintf(buffer, "%u B", size);
    } else if (size < 1024 * 1024) {
        sprintf(buffer, "%.1f KB", (float)size / 1024);
    } else if (size < 1024 * 1024 * 1024) {
        sprintf(buffer, "%.1f MB", (float)size / (1024 * 1024));
    } else {
        sprintf(buffer, "%.1f GB", (float)size / (1024 * 1024 * 1024));
    }
}

void get_attr_string(BYTE attr, char* buffer) {
    sprintf(buffer, "%c%c%c%c%c",
            (attr & AM_RDO) ? 'R' : '-',  // Read-only
            (attr & AM_HID) ? 'H' : '-',  // Hidden
            (attr & AM_SYS) ? 'S' : '-',  // System
            (attr & AM_ARC) ? 'A' : '-',  // Archive
            (attr & AM_DIR) ? 'D' : '-'); // Directory
}

#define WRITE_SIZE_MB (16UL)
#define WRITE_SIZE (1024UL * 1024UL * WRITE_SIZE_MB)
#define BUF_SIZE (1024UL * 16UL)

uint8_t sdio_read_write_buffer[BUF_SIZE];

#if SDIO_TEST_MODE == 0
void sdio_task(void *param)
{
    FRESULT res_sd = 0;
    FATFS fs;
    FIL fnew;
    UINT fnum;
    
    const char *tmp_data = "cherryusb fatfs demo...\r\n";

    DDL_Printf("data len:%d\r\n", (unsigned int)strlen(tmp_data));
    for (uint32_t i = 0; i < 100; i++) {
        memcpy(&sdio_read_write_buffer[i * 25], tmp_data, strlen(tmp_data));
    }

    res_sd = f_mount(&fs, "1:", 1);
    if (res_sd != FR_OK) {
        DDL_Printf("mount fail,res:%d\r\n", (unsigned int)res_sd);
        vTaskDelete(NULL);
        return;
    }

    DDL_Printf("test fatfs write\r\n");
    res_sd = f_open(&fnew, "1:test.txt", FA_CREATE_ALWAYS | FA_WRITE);
    if (res_sd == FR_OK) {
        res_sd = f_write(&fnew, sdio_read_write_buffer, sizeof(sdio_read_write_buffer), &fnum);
        if (res_sd == FR_OK) {
            DDL_Printf("write success, write len:%d\n", fnum);
        } else {
            DDL_Printf("write fail\r\n");
            goto unmount;
        }
        f_close(&fnew);
    } else {
        DDL_Printf("open fail\r\n");
        goto unmount;
    }
    DDL_Printf("test fatfs read\r\n");

    res_sd = f_open(&fnew, "1:test.txt", FA_OPEN_EXISTING | FA_READ);
    if (res_sd == FR_OK) {
        res_sd = f_read(&fnew, sdio_read_write_buffer, sizeof(sdio_read_write_buffer), &fnum);
        if (res_sd == FR_OK) {
            DDL_Printf("read success, read len:%d\n", fnum);
        } else {
            DDL_Printf("read fail\r\n");
            goto unmount;
        }
        f_close(&fnew);
    } else {
        DDL_Printf("open fail\r\n");
        goto unmount;
    }

    for (uint32_t i = 0; i < BUF_SIZE; i++) {
        sdio_read_write_buffer[i] = i % 256;
    }

    DDL_Printf("test fatfs write speed\r\n");
    res_sd = f_open(&fnew, "1:cherryusb_msc_test.bin", FA_OPEN_ALWAYS | FA_WRITE);
    if (res_sd == FR_OK) {
        uint32_t write_size = WRITE_SIZE;
        uint32_t start_time = (uint32_t)xTaskGetTickCount();
        while (write_size > 0) {
            res_sd = f_write(&fnew, sdio_read_write_buffer, BUF_SIZE, (UINT*)&fnum);
            if (res_sd != FR_OK) {
                printf("Write file failed, cause: %d\n", res_sd);
                goto unmount;
            }
            write_size -= BUF_SIZE;
        }
        if (res_sd == FR_OK) {
            uint32_t time_ms = xTaskGetTickCount() - start_time;
            DDL_Printf("Fatfs write speed:%f MB/S\r\n", (WRITE_SIZE_MB * 1000 / (float)time_ms));
        } else {
            DDL_Printf("write fail\r\n");
            goto unmount;
        }
        f_close(&fnew);
    } else {
        DDL_Printf("open fail\r\n");
        goto unmount;
    }
    DDL_Printf("test fatfs read speed\r\n");

    res_sd = f_open(&fnew, "1:cherryusb_msc_test.bin", FA_OPEN_EXISTING | FA_READ);
    if (res_sd == FR_OK) {
        uint32_t write_size = WRITE_SIZE;
        uint32_t start_time = (uint32_t)xTaskGetTickCount();
        while (write_size > 0) {
            res_sd = f_read(&fnew, sdio_read_write_buffer, BUF_SIZE, (UINT*)&fnum);
            if (res_sd != FR_OK) {
                printf("Read file failed, cause: %d\n", res_sd);
                goto unmount;
            }
            write_size -= BUF_SIZE;
        }
        if (res_sd == FR_OK) {
            uint32_t time_ms = xTaskGetTickCount() - start_time;
            DDL_Printf("Fatfs read speed:%f MB/S\r\n", (WRITE_SIZE_MB * 1000 / (float)time_ms));
        } else {
            DDL_Printf("read fail\r\n");
            goto unmount;
        }
        f_close(&fnew);
    } else {
        DDL_Printf("open fail\r\n");
        goto unmount;
    }
    
unmount:
    f_mount(NULL, "1:", 1);
    vTaskDelete(NULL);
}

#elif SDIO_TEST_MODE == 1
void sdio_task(void *param)
{
    FRESULT res_sd = 0;
    FATFS fs;
    FATFS *pfs = &fs;
    const MKFS_PARM mkfs_opt = {FM_EXFAT, 0, 0, 0, 0};
    const char* path = "1:";
    
    res_sd = f_mount(&fs, path, 1);
    if (res_sd == FR_NO_FILESYSTEM) {
        // No filesystem found, need to format
        DDL_Printf("No filesystem found, formatting...\n");
        
        // Format the drive
        res_sd = f_mkfs(path, &mkfs_opt, NULL, FF_MAX_SS);
        if (res_sd != FR_OK) {
            DDL_Printf("Format failed: %d\n", res_sd);
            vTaskDelete(NULL);
        }
        
        DDL_Printf("Format successful, remounting...\n");
        
        // Remount after formatting
        res_sd = f_mount(&fs, "1:", 0);
        if (res_sd != FR_OK) {
            DDL_Printf("Remount after format failed: %d\n", res_sd);
            vTaskDelete(NULL);
        }
        
        DDL_Printf("Filesystem mounted successfully after format\n");
    }
    else if (res_sd != FR_OK) {
        // Other mount errors
        DDL_Printf("Mount failed with error: %d\n", res_sd);
        vTaskDelete(NULL);
    }
    else {
        // Mount successful
        DDL_Printf("Filesystem mounted successfully\n");
    }

    DWORD fre_clust;
    res_sd = f_getfree(path, &fre_clust, &pfs);
    if (res_sd != FR_OK)
    {
        goto unmount;
    }

    printf("\n=== Detailed Filesystem Information ===\n");
    printf("Drive: %s\n", path);
    printf("Filesystem Type: %s\n", 
           (pfs->fs_type == FS_FAT12) ? "FAT12" :
           (pfs->fs_type == FS_FAT16) ? "FAT16" :
           (pfs->fs_type == FS_FAT32) ? "FAT32" :
           (pfs->fs_type == FS_EXFAT) ? "exFAT" : "Unknown");
    
    printf("Sector Size: %u bytes\n", FF_MAX_SS);
    printf("Cluster Size: %d sectors (%d bytes)\n", 
           pfs->csize, pfs->csize * FF_MAX_SS);
    
    printf("Number of FATs: %u\n", pfs->n_fats);
    printf("FAT Start Sector: %u\n", pfs->fatbase);
    printf("Root Directory Start: %u\n", pfs->dirbase);
    printf("Data Area Start: %u\n", pfs->database);
    
    DWORD tot_clust = pfs->n_fatent - 2;
    DWORD used_clust = tot_clust - fre_clust;
    
    printf("Total Clusters: %u\n", tot_clust);
    printf("Used Clusters: %u\n", used_clust);
    printf("Free Clusters: %u\n", fre_clust);
    
    // Calculate sizes safely to avoid overflow
    uint64_t tot_size = (uint64_t)tot_clust * pfs->csize * FF_MAX_SS;
    uint64_t free_size = (uint64_t)fre_clust * pfs->csize * FF_MAX_SS;
    uint64_t used_size = (uint64_t)used_clust * pfs->csize * FF_MAX_SS;
    
    printf("\nSize Information:\n");
    printf("Total: %llu bytes (%.2f MB)\n", tot_size, (float)tot_size / (1024 * 1024));
    printf("Used:  %llu bytes (%.2f MB)\n", used_size, (float)used_size / (1024 * 1024));
    printf("Free:  %llu bytes (%.2f MB)\n", free_size, (float)free_size / (1024 * 1024));
    
    if (tot_size > 0) {
        float free_percent = 100.0f * free_size / tot_size;
        printf("Free Space: %.1f%%\n", free_percent);
    }
    
    DIR dir;
    FILINFO fno;
    int nfile = 0, ndir = 0;
    uint64_t total_size = 0;
    char time_str[32], size_str[32], attr_str[8];
    res_sd = f_opendir(&dir, path);                   /* Open the directory */
    if (res_sd != FR_OK) {
        printf("Failed to open directory \"%s\". (%u)\n", path, res_sd);
        goto unmount;
    }
    // Print header
    printf("Attributes  Size        Date Modified           Name\n");
    printf("----------  ----------  ---------------------  --------------------\n");
    for (;;) {
        res_sd = f_readdir(&dir, &fno);           /* Read a directory item */
        if (res_sd != FR_OK || fno.fname[0] == 0) break;
        // Skip current and parent directory entries
        if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || 
            (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
            continue;
        }
            
        // Convert time and format size
        fat_time_to_str(fno.fdate, fno.ftime, time_str);
        get_attr_string(fno.fattrib, attr_str);
            
        if (fno.fattrib & AM_DIR) {
            // Directory entry
            format_file_size(0, size_str);
            printf("%s  %-10s  %s  %s/\n", 
                   attr_str, size_str, time_str, fno.fname);
            ndir++;
        } else {
            // File entry
            format_file_size(fno.fsize, size_str);
            printf("%s  %-10s  %s  %s\n", 
                   attr_str, size_str, time_str, fno.fname);
            nfile++;
            total_size += fno.fsize;
        }
    }
    f_closedir(&dir);
    // Print summary
    printf("----------  ----------  ---------------------  --------------------\n");
    printf("Total: %d directories, %d files, ", ndir, nfile);
    format_file_size(total_size, size_str);
    printf("%s total file size\n", size_str);
    
unmount:
    f_mount(NULL, "1:", 1);
    vTaskDelete(NULL);
}

#elif SDIO_TEST_MODE == 2
void sdio_task(void *param)
{
    FRESULT res_sd = 0;
    FATFS fs;
    FATFS *pfs = &fs;
    FIL fnew;
    const char* path = "1:";
    
    FILINFO file_info;
    DIR dir;
    
    res_sd = f_mount(&fs, path, 1);
    if(res_sd != FR_OK)
    {
        DDL_Printf("Mount failed with error: %d\n", res_sd);
        vTaskDelete(NULL);
    }
    else
    {
        // Mount successful
        DDL_Printf("Filesystem mounted successfully\n");
    }
    
    res_sd = f_opendir(&dir, "1:/");
    if (res_sd != FR_OK)
    {
        goto unmount;
    }
    while (1)
    {
        res_sd = f_readdir(&dir, &file_info);
        if (res_sd != FR_OK)
        {
            printf("read failed\n");
            goto unmount;
        }
        if (file_info.fname[0] == '\0')
        {
            break;
        }
        printf("%s\n", file_info.fname);
    }
    f_rewinddir(&dir);
    f_closedir(&dir);

    // res_sd = f_unlink("1:/sync.txt");
    // if (res_sd == FR_OK)
    // {
    //     printf("remove ok\n");
    // }

    res_sd = f_mkdir("1:/test");
    if (res_sd == FR_OK)
    {
        printf("mkdir ok\n");
    }
    
unmount:
    f_mount(NULL, "1:", 1);
    vTaskDelete(NULL);
}
#endif // TEST_MODE
